﻿/// <reference path="mousestate.ts" />

module UI {

    export var COLOR_TEXT: string = '#eee';
    export var COLOR_HOVER: string = '#33e';
    export var COLOR_DOWN_OUTLINE: string = '#f22';
    export var COLOR_DOWN_FILL: string = '#f44';
    export var COLOR_DOWN_TEXT: string = '#fff';
    export var COLOR_DISABLED: string = '#bbb';
    export var COLOR_TEXT_DARK: string = '#333';
    export var COLOR_TEXT_LIGHT: string = '#fff';
    export var COLOR_FACE: string = '#eee';

    export var COLOR_WHITE: string = '#fff';
    export var COLOR_BLACK: string = '#000';

    export var FONT_NORMAL: string = '14px Consolas';
    export var FONT_LARGE: string = '18px Consolas';

    //#warning Work in progress!

    export var theme = {
        colors: {
            black: '#000',
            charm: '#33c',
            default: {
                outline: '#fff',
                face: '#eee',
                faceText: '#333',
                text: '#eee'
            },
            hover: {
                outline: '#eee',
                face: '#fff',
                faceText: '#444',
                text: '#eee'
            },
            down: {
                outline: '#ddd',
                face: '#aaa',
                faceText: '#222',
                text: '#eee'
            },
            disabled: {
                outline: '#444',
                face: '#444',
                faceText: '#222',
                text: '#666'
            }
        },
        fonts: {
            normal: '14px Consolas',
            large: '18px Consolas'
        }
    };

    export var hoverControl: UIControl = null;
    export var activeControl: UIControl = null;
    export var mouse: MouseState = new MouseState();
    export var root: UIControl = null;
    export var activeUI: ISwitchableUI = null;

    export function setupControl(control: UIControl, details: any) {

        if (control.computed === undefined || control.computed === null) {
            control.computed = new ComputedState();
        }

        control.computed.state = ControlState.up;
        control.computed.mouse = null;
        control.enabled = true;
        control.visible = true;

        if (details === undefined || details === null) return;
        for (var propertyName in details) {
            control[propertyName] = details[propertyName];
        }

    }

    export function compute(control: UIControl, bounds: Bounds) {

        var leftDefined = control.left !== undefined;
        var topDefined = control.top !== undefined;
        var rightDefined = control.right !== undefined;
        var bottomDefined = control.bottom !== undefined;
        var widthDefined = control.width !== undefined;
        var heightDefined = control.height !== undefined;

        var c = control.computed;

        // Align horizontally
        if (leftDefined && widthDefined) {
            c.left = bounds.left + control.left;
            c.width = control.width;
            c.right = c.left + c.width;
        }
        else if (rightDefined && widthDefined) {
            c.width = control.width;
            c.right = bounds.left + bounds.width - control.right;
            c.left = c.right - c.width;
        }
        else if (leftDefined && rightDefined && !widthDefined) {
            c.left = bounds.left + control.left;
            c.right = bounds.left + bounds.width - control.right;
            c.width = c.right - c.left;
        }
        else if (!leftDefined && !rightDefined && widthDefined) {
            c.width = control.width;
            c.left = bounds.left + bounds.width / 2 - c.width / 2;
            c.right = c.left + c.width;
        }
        else if (!leftDefined && !rightDefined && !widthDefined) {
            c.left = bounds.left;
            c.width = bounds.width;
            c.right = c.left + c.width;
        }

        // Align vertically
        if (topDefined && heightDefined) {
            c.top = bounds.top + control.top;
            c.height = control.height;
            c.bottom = c.top + c.height;
        }
        else if (bottomDefined && heightDefined) {
            c.height = control.height;
            c.bottom = bounds.top + bounds.height - control.bottom;
            c.top = c.bottom - c.height;
        }
        else if (topDefined && bottomDefined && !heightDefined) {
            c.top = bounds.top + control.top;
            c.bottom = bounds.top + bounds.height - control.bottom;
            c.height = c.bottom - c.top;
        }
        else if (!topDefined && !bottomDefined && heightDefined) {
            c.height = control.height;
            c.top = bounds.top + bounds.height / 2 - c.height / 2;
            c.bottom = c.top + c.height;
        }
        else if (!topDefined && !bottomDefined && !heightDefined) {
            c.top = bounds.top;
            c.height = bounds.height;
            c.bottom = c.top + c.height;
        }

        // Fill in the remaining calculations
        c.center = (c.left + c.width / 2) | 0;
        c.middle = (c.top + c.height / 2) | 0;

        // Recurse into child controls
        if (control.controls !== undefined) {
            for (var i = 0; i < control.controls.length; i++) {
                UI.compute(control.controls[i], control.computed);
            }
        }

    }

    export function getControlByPosition<T extends UIControl>(parent: UIControl, x: number, y: number): T {
        return UI.getControl<T>(parent,
            function (control) { return control.visible && !control.transparent && x >= control.computed.left && y >= control.computed.top && x <= control.computed.right && y <= control.computed.bottom },
            function (control) { return control.visible });
    }

    export function getControlParent(parent: UIControl, control: UIControl): UIControl {
        for (var i in parent.controls) {
            var child = parent.controls[i];
            if (child === control) {
                return parent;
            }
            if (child.controls.length > 0) {
                var possibleResult = getControlParent(child, control);
                if (possibleResult != null) return possibleResult;
            }
        }
        return null;
    }

    export function $<T extends UIControl>(name: string): T {
        return UI.getControl<T>(UI.root, function (control) { return control.name == name; });
    }

    export function getControlByName<T extends UIControl>(parent: UIControl, name: string): T {
        return UI.getControl<T>(parent, function (control) { return control.name == name; });
    }

    export function getControl<T extends UIControl>(parent: UIControl,
        candidateFunction: (parent: UIControl) => boolean,
        recurseFunction: (parent: UIControl) => void = undefined): T {
        if (parent == null) return null;

        // Recurse into child controls    
        if (parent.controls !== undefined && parent.controls !== null) {
            for (var i = 0; i < parent.controls.length; i++) {
                var child = parent.controls[i];
                if (recurseFunction === undefined || recurseFunction(child)) {
                    var childControl = UI.getControl(child, candidateFunction);
                    if (childControl !== null) return <T>childControl;
                }
            }
        }

        // Check this control
        if (candidateFunction(parent)) {
            return <T>parent;
        }
        return null;

    }

    export function handleMouseDown(x: number, y: number) {
        var control = UI.getControlByPosition(UI.root, x, y);
        var handled = control !== null;
        UI.mouse = { x: x, y: y };
        UI.hoverControl = control;
        if (control !== null) {
            if (control.enabled) {
                control.computed.state = ControlState.down;
            }
        }
        return handled;
    }

    export function handleMouseMove(x: number, y: number) {
        var control = UI.getControlByPosition(UI.root, x, y);
        UI.mouse = { x: x, y: y };
        if (UI.hoverControl !== null) {
            if (UI.hoverControl.onExit !== undefined && UI.hoverControl.onExit !== null) {
                UI.hoverControl.onExit(UI.hoverControl, InputArgs.createMouse(UI.mouse));
            }
            UI.hoverControl.computed.state = ControlState.up;
            UI.hoverControl.computed.mouse = null;
        }
        if (control !== null && control.enabled) {
            if (control.onEnter !== undefined && control.onEnter !== null) {
                control.onEnter(control, InputArgs.createMouse(UI.mouse));
            }
            UI.hoverControl = control;
            control.computed.state = ControlState.hover;
            control.computed.mouse = { x: x - control.computed.left, y: y - control.computed.top };
        }
    }

    export function handleMouseUp(x: number, y: number) {
        var control = UI.getControlByPosition(UI.root, x, y);
        UI.mouse = { x: x, y: y };
        UI.hoverControl = control;
        if (control !== null && control.enabled) {
            control.computed.state = ControlState.hover;
        }
    }

    export function handleClick(x: number, y: number): boolean {
        UI.mouse = { x: x, y: y };
        var control = UI.getControlByPosition(UI.root, x, y);
        if (control !== null && control.enabled) {
            UI.activeControl = control;
            if (control.onClick !== undefined && control.onClick !== null) {
                control.onClick(control, InputArgs.createMouse(UI.mouse));
            }
            if (control.handleClick !== undefined && control.handleClick !== null) {
                control.handleClick(control, InputArgs.createMouse(UI.mouse));
            }
            return !control.transparent;
        }
        return false;
    }

    export function handleKeyDown(e: KeyboardEvent) {
        var active = UI.activeControl;
        if (active !== null && active.enabled) {
            if (active.onKeyDown !== undefined && active.onKeyDown !== null) {
                active.onKeyDown(active, InputArgs.createKey(e));
            }
            if (active.handleKeyDown !== undefined && active.handleKeyDown !== null) {
                active.handleKeyDown(active, InputArgs.createKey(e));
                return true;
            }
        }
        return false;
    }

    export function handleKeyUp(e: KeyboardEvent) {
        var active = UI.activeControl;
        if (active !== null && active.enabled && active.handleKeyUp !== undefined && active.handleKeyUp !== null) {
            active.handleKeyUp(active, InputArgs.createKey(e));
            return true;
        }
        return false;
    }

    export function handleKeyPress(e: KeyboardEvent) {
        var active = UI.activeControl;
        if (active !== null && active.enabled && active.handleKeyPress !== undefined && active.handleKeyPress !== null) {
            active.handleKeyPress(active, InputArgs.createKey(e));
            return true;
        }
        return false;
    }


    export function getKey(e: KeyboardEvent): string {
        if (e.key !== undefined) return e.key;
        switch (e.keyCode) {
            case 0: return '';
            case 8: return 'Backspace';
            case 9: return 'Tab';
            case 13: return 'Enter';
            case 16: return 'Shift';
            case 17: return 'Control';
            case 18: return 'Alt';
            case 19: return 'Pause';
            case 20: return 'CapsLock';
            case 27: return 'Escape';
            case 33: return 'PageUp';
            case 34: return 'PageDown';
            case 35: return 'End';
            case 36: return 'Home';
            case 37: return 'ArrowLeft';
            case 38: return 'ArrowUp';
            case 39: return 'ArrowRight';
            case 40: return 'ArrowDown';
            case 45: return 'Insert';
            case 46: return 'Delete';
            case 96: return e.shiftKey ? ')' : '0';
            case 97: return e.shiftKey ? '!' : '1';
            case 98: return e.shiftKey ? '"' : '2';
            case 99: return e.shiftKey ? '£' : '3';
            case 100: return e.shiftKey ? '$' : '4';
            case 101: return e.shiftKey ? '%' : '5';
            case 102: return e.shiftKey ? '^' : '6';
            case 103: return e.shiftKey ? '&' : '7';
            case 104: return e.shiftKey ? '*' : '8';
            case 105: return e.shiftKey ? '(' : '9';
            case 187: return e.shiftKey ? '+' : '=';
            case 106: return e.shiftKey ? '*' : '*';
            case 107: return e.shiftKey ? '+' : '+';
            case 109: return e.shiftKey ? '_' : '-';
            case 110: return e.shiftKey ? '>' : '.';
            case 111: return e.shiftKey ? '?' : '/';
            case 112: return 'F1';
            case 113: return 'F2';
            case 114: return 'F3';
            case 115: return 'F4';
            case 116: return 'F5';
            case 117: return 'F6';
            case 118: return 'F7';
            case 119: return 'F8';
            case 120: return 'F9';
            case 121: return 'F10';
            case 122: return 'F11';
            case 123: return 'F12';
            case 173: return e.shiftKey ? '_' : '-';
            case 186: return e.shiftKey ? ':' : ';';
            case 187: return e.shiftKey ? '+' : '=';
            case 188: return e.shiftKey ? '<' : ',';
            case 189: return e.shiftKey ? '_' : '-';
            case 190: return e.shiftKey ? '>' : '.';
            case 191: return e.shiftKey ? '?' : '/';
            case 219: return e.shiftKey ? '{' : '[';
            case 220: return e.shiftKey ? '|' : '\\';
            case 221: return e.shiftKey ? '}' : ']';
            case 222: return e.shiftKey ? '@' : '\'';
            default:
                var char = String.fromCharCode(e.keyCode);
                return e.shiftKey ? char : char.toLowerCase();
        }
    }

    export function attachEvents(canvas: HTMLCanvasElement): void {
        canvas.onmousedown = function (e) { UI.handleMouseDown(e.pageX, e.pageY); };
        canvas.onmousemove = function (e) { UI.handleMouseMove(e.pageX, e.pageY); };
        canvas.onmouseup = function (e) { UI.handleMouseUp(e.pageX, e.pageY); };
        canvas.onclick = function (e) { UI.handleClick(e.pageX, e.pageY); };
        window.onkeydown = function (e: KeyboardEvent) { UI.handleKeyDown(e); };
        window.onkeyup = function (e: KeyboardEvent) { UI.handleKeyUp(e); };
        window.onkeypress = function (e: KeyboardEvent) { UI.handleKeyPress(e); };
    }

    export function compile(parentNode: Object): Array<UIControl> {

        var newControls = [];
        for (var controlName in parentNode) {
            var node = parentNode[controlName];
            var parts = controlName.split(/(?=[A-Z])/);

            // Create a new control of the appropriate type
            var controlType = '';
            var newControl = null;
            for (var i = parts.length - 1; i > 0; i--) {
                controlType = parts[i] + controlType;
                if (UI[controlType] !== undefined) {
                    newControl = new UI[controlType]();
                }
            }

            // Did we create an appropriate control?
            if (newControl === null) {
                throw 'Could not determine control type of control "' + controlName + '".';
            }

            // Set properties
            newControl.name = controlName;
            for (var propertyName in node) {
                if (propertyName != 'controls') {
                    var propertyValue = node[propertyName];
                    newControl[propertyName] = propertyValue;
                }
            }

            // Recurse into child controls        
            if (node.controls !== undefined) {
                var childControls = UI.compile(node.controls);
                for (var childControlKey in childControls) {
                    var childControl = childControls[childControlKey];
                    newControl.controls.push(childControl);
                }
            }

            // Add to list
            newControls.push(newControl);

        }

        // Return
        return newControls;

    }

    export function swap(newUI: ISwitchableUI, screenBounds: Bounds) {
        var oldUI = activeUI;
        if (oldUI !== null && oldUI.hide !== undefined) {
            oldUI.hide();
        }
        activeUI = newUI;
        UI.root = newUI === null ? null : newUI.root;
        if (UI.root !== null) {
            UI.compute(UI.root, screenBounds);
        }
        if (activeUI !== null && activeUI.show !== undefined) {
            activeUI.show(oldUI);
        }
    }

    export function render(context: CanvasRenderingContext2D) {
        if (UI.root === null) return;

        context.fillStyle = '#fff';
        context.strokeStyle = '#000';
        context.lineWidth = 2;
        context.textBaseline = 'top';
        context.textAlign = 'left';
        context.font = UI.FONT_NORMAL;

        var stack = [UI.root];
        while (stack.length > 0) {
            var control = stack.pop();
            if (control.render !== undefined && control.visible) {
                control.render(context);
            }
            if (control.controls !== undefined) {
                for (var i = 0; i < control.controls.length; i++) {
                    var controlToPush = control.controls[i];
                    if (controlToPush.visible) {
                        stack.push(controlToPush);
                    }
                }
            }
        }

    }

    export function continuousRendering(context: CanvasRenderingContext2D) {
        function regularRendering() {
            window.requestAnimationFrame(regularRendering);
            render(context);
        };
        regularRendering();
    }

    export function renderText(context: CanvasRenderingContext2D, x: number, y: number, align: string, baseline: string, text: string) {
        context.textBaseline = baseline;
        context.textAlign = align;
        context.font = '14px Consolas';
        context.fillStyle = UI.COLOR_TEXT_LIGHT;
        context.fillText(text, x, y);
    }

} 